// file:system/task/task.c
// time:2021.8.11
// autor:jiangxinpeng
// copyright:(C) by jiangxinpeng,All right are reserved.

#include <os/vmm.h>
#include <os/process.h>
#include <os/task.h>
#include <os/fd.h>
#include <os/schedule.h>
#include <os/debug.h>
#include <os/kernel.h>
#include <os/safety.h>
#include <os/environ.h>
#include <os/timer.h>
#include <os/account.h>

#include <arch/interrupt.h>
#include <arch/pymem.h>
#include <arch/config.h>
#include <arch/task.h>
#include <arch/vmm.h>
#include <sys/proc.h>

#include <lib/type.h>
#include <lib/list.h>
#include <lib/errno.h>
#include <lib/assert.h>

static pid_t task_pid = 0; // task pid next
static char *init_argv[2] = {"/sbin/init", NULL};
char *__envp = NULL;

// global define
LIST_HEAD(task_global_list);
DEFINE_SPIN_LOCK(task_lock);
volatile int task_init_done = 0;

// init idle task
static void TaskInitIdle(sched_unit_t *su)
{
    su->idle = (task_t *)MEM_KERNEL_STACK_BOTTOM;
    TaskInit(su->idle, "idle0", TASK_PRIOR_LEVEL_HIGH);
    // init fd
    if (FsFdInit(su->idle) < 0)
    {
        Panic("init kernel main process fd failed\n");
    }
    // set to schedule unit current task
    su->cur = su->idle;
    KPrint("[task] init a idle task ok\n");
}

// kernel idle
static inline void KernelDoIdle()
{
    while (1)
        ;
}

// init kernel task
void TaskInitKernel()
{
    // task start pid init
    task_pid = 0;
    sched_unit_t *su = SchedGetCurUnit();
    TaskInitIdle(su);
    // pid 1 reserved to init process
    TaskTakePid();
    task_init_done = 1;
    KPrint("kernel task init done.\n");
}

// take a pid
int TaskTakePid()
{
    return task_pid++;
}

// free a pid
int TaskRollBackPid()
{
    return --task_pid;
}

void TaskInit(task_t *task, char *name, uint8_t level)
{
    memset(task, 0, sizeof(task_t));
    strcpy(task->name, name);
    task->cpuid = task->last_cpuid = -1;
    task->status = TASK_READY;
    // no init user,default uid and gid is 0
    task->uid = (!task_init_done) ? 0 : cur_user->uid;
    task->gid = (!task_init_done) ? 0 : cur_user->gid;
    SpinLockInit(&task->lock);
    task->static_priority = level;
    task->priority = task->static_priority;
    list_init(&task->list);
    list_init(&task->global_list);
    task->timeslice = TASK_TIMESLICE_BASE + 1;
    task->ticks = task->timeslice;
    task->elapsed_ticks = 0;
    task->syscall_ticks = task->syscall_ticks_delta = 0;
    task->vmm = NULL;
    task->pid = TaskTakePid();
    task->threadgroup_id = task->pid;
    task->processgroup_id = -1;
    task->parent_pid = -1;
    task->exit_status = 0;
    task->kstack = (uint8_t *)((uint8_t *)task + TASK_KERNEL_STACK_SIZE);
    task->flags = 0;
    // init fpu
    FpuInit(&task->fpu, 1);
    // init timer
    TimerInit(&task->sleep_time, 0, NULL, NULL);
    // init alarm
    AlarmInit(&task->alarm);
    // exception manager init
    ExceptionManagerInit(&task->exception_manager);
    task->errno = 0;
    task->pthread = NULL;
    task->fileman = NULL;
    task->exit_hook_arg = NULL;
    task->exit_hook = NULL;
    task->port_comm = NULL;
    task->stack_magic = TASK_STACK_MAGIC;
}

void TaskActive(task_t *task)
{
    SpinLockDisInterrupt(&task->lock);
    task->status = TASK_RUNNING;
    SpinUnlockEnInterrupt(&task->lock);
    VmmActive(task->vmm);
}

void TaskBlock(int status)
{
    task_t *cur = cur_task;

    if (cur == SchedGetCurUnit()->idle) // no block kernel
        return;

    SpinLockDisInterrupt(&cur->lock);
    if (status == TASK_BLOCKED || status == TASK_WAITTING || status == TASK_STOPPED || status == TASK_HANGING || status == TASK_ZOMBIE)
    {
        cur->status = status;
        SpinUnlockEnInterrupt(&cur->lock);
        SchedQueueRemoveTask(SchedGetCurUnit(), cur);
        // schedule new task
        Schedule();
    }
}

void TaskUnBlock(task_t *task)
{
    sched_unit_t *su = NULL;

    if (task == SchedGetCurUnit()->idle)
        return;

    SpinLockDisInterrupt(&task->lock);
    if (!(task->status == TASK_BLOCKED || task->status == TASK_WAITTING || task->status == TASK_STOPPED || task->status == TASK_HANGING || task->status == TASK_ZOMBIE))
    {
        // task status error
        SpinUnlockEnInterrupt(&task->lock);
        return;
    }

    if (task->status != TASK_READY)
    {
        su = SchedGetCurUnit();
        if (SchedQueueHadTask(su, task))
        {
            KPrint("[task] task %s had in su!\n", task->name);
            // task had in schedlue list
            SpinUnlockEnInterrupt(&task->lock);
            return;
        }
        task->status = TASK_READY;
        task->priority = SchedCalcNewPriority(task, 1);
        SpinUnlockEnInterrupt(&task->lock);
        // move to new queue
        SchedQueueAddTaskTail(su, task);
    }
}

void TaskReady(task_t *task)
{
    SpinLock(&task_lock);
    task->status = TASK_READY;
    SpinUnlock(&task_lock);
}

void TaskBlockTarge(task_t *task, int status)
{
    if (!task)
        return;

    SpinLock(&task_lock);
    if (status == TASK_BLOCKED || status == TASK_WAITTING || status == TASK_STOPPED || status == TASK_HANGING || status == TASK_ZOMBIE)
    {
        task->status = status;
        SchedQueueRemoveTask(SchedGetCurUnit(), task);
    }
    SpinUnlock(&task_lock);
}

// wakeup task in waitting status
int TaskWakeUp(task_t *task)
{
    // resure task is block status
    if (TASK_NOT_READY(task))
    {
        if (TASk_IN_WAITLIST(task))
        {
            TASK_EXIT_WAITLIST(task);
            list_del(&task->list);
        }
        // task unblock
        TaskUnBlock(task);
        return 0;
    }
    return -1;
}


void TaskYield()
{
    SpinLockDisInterrupt(&cur_task->lock);
    cur_task->status = TASK_READY;
    SpinUnlockEnInterrupt(&cur_task->lock);
    Schedule();
}

void SysTaskYield()
{
    TaskYield();
}

pid_t TaskGetPid(task_t *task)
{
    return task->threadgroup_id;
}

// if task is process,thread group id=pid
// or,take is thread, thread group id=master process pid
pid_t SysGetPid()
{
    return TaskGetPid(cur_task);
}

// get parient pid
pid_t SysGetParPid()
{
    return cur_task->parent_pid;
}

pid_t SysGetThreadPid()
{
    return cur_task->pid;
}

int SysSetProcessGroupId(pid_t pid, pid_t pgid)
{
    task_t *task = NULL;
    task_t *cur = cur_task;

    if (pid < 0 || pgid < 0)
        return -EINVAL;
    if (!pid)
    {
        // pid=0 set current task pgid
        task = cur;
    }
    else
    {
        task = TaskFindByPid(pid);
        if (!task)
        {
            return -ESRCH;
        }
    }
    if (!pgid)
        pgid = task->processgroup_id;
    // no self or self child process
    if (task->pid != cur->pid || !TaskIsChild(cur->pid, task->pid))
    {
        return -EPERM;
    }
    // set task process group id
    task->processgroup_id = pgid;
    return 0;
}

int SysGetProcessGroupId(pid_t pid)
{
    task_t *task;

    if (pid < 0)
        return -EINVAL;
    if (!pid)
        task = cur_task;
    else
    {
        task = TaskFindByPid(pid);
        if (!task)
        {
            return -ESRCH;
        }
    }
    return task->processgroup_id;
}

task_t *TaskFindByPid(pid_t pid)
{
    task_t *task;
    SpinLock(&task_lock);
    list_traversal_all_owner_to_next(task, &task_global_list, global_list)
    {
        if (task->pid == pid)
        {
            SpinUnlock(&task_lock);
            return task;
        }
    }
    SpinUnlock(&task_lock);
    return NULL;
}

task_t *TaskFindByName(char *name)
{
    task_t *task;

    SpinLock(&task_lock);
    list_traversal_all_owner_to_next(task, &task_global_list, global_list)
    {
        if (!strcmp(task->name, name))
        {
            SpinUnlock(&task_lock);
            return task;
        }
    }
    SpinUnlock(&task_lock);
    return NULL;
}

void TaskAddToGlobalList(task_t *task)
{
    SpinLock(&task_lock);
    list_add_tail(&task->global_list, &task_global_list);
    SpinUnlock(&task_lock);
}

int TaskIsChild(pid_t pid, pid_t child_pid)
{
    task_t *child = TaskFindByPid(pid);
    if (!child)
        return 0;
    return (child->parent_pid == pid);
}

void TaskFree(task_t *task)
{   
    SpinLock(&task_lock);
    if (!list_empty(&task->global_list))
        list_del_init(&task->global_list);
    KMemFree(task);
    SpinUnlock(&task_lock);
}

task_t *TaskCreate(char *name, uint32_t prior, task_func_t fun, void *arg)
{
    task_t *task = KMemAlloc(TASK_KERNEL_STACK_SIZE);
    sched_unit_t *su = SchedGetCurUnit();
    if (!task)
        return NULL;

    TaskInit(task, name, prior);
    task->flags |= THREAD_KERNEL;

    // init fd
    if (FsFdInit(task) < 0)
    {
        KMemFree(task);
        return NULL;
    }
    // build stack
    TaskStackBuild(task, fun, arg);
    // add to task schedule list
    TaskAddToGlobalList(task);

    EnInterrupt();

#ifdef ENABLE_SMP
    #ifdef ENABLE_SMP_SCHEDULE
    // dispatch task to free cpu
    TaskDispatch(task);
    #else
    SchedQueueAddTaskTail(su,task);
    #endif
#else
    SchedQueueAddTaskTail(su, task);
#endif
    return task;
}

void TaskExit(int status)
{
    uint32_t flags;
    task_t *cur = cur_task;
    if (cur->pid == USER_INIT_PROCESS_ID)
    {
        KPrint("[task] init process can't exit!\n");
        return;
    }
    cur->exit_status = status;
    TaskCancelTimer(cur);
    TaskExitHook(cur);
    cur->parent_pid = USER_INIT_PROCESS_ID;
    task_t *parent = TaskFindByPid(cur->parent_pid);
    if (parent)
    {
        if (parent->status == TASK_WAITTING)
        {
            // handing task and wakeup parent task
            TaskUnBlock(parent);
            TaskBlock(TASK_HANGING);
        }
        else
        {
            TaskBlock(TASK_ZOMBIE);
        }
    }
    else
    {
        TaskBlock(TASK_ZOMBIE);
    }
    Panic("task exit exception!\n");
}

// task cancel timer
int TaskCancelTimer(task_t *task)
{
    TimerCancel(&task->sleep_time);
    return 0;
}

int TaskCountChild(task_t *parent)
{
    task_t *child;
    uint32_t count = 0;

    list_traversal_all_owner_to_next(child, &task_global_list, global_list)
    {
        if (child->parent_pid == parent->pid)
            count++;
    }
    return count;
}

int TaskSetCwd(task_t *task, const char *path)
{
    if (!task || !path)
        return -EINVAL;
    memset(task->fileman->cwd, 0, MAX_PATH_LEN+1);
    memcpy(task->fileman->cwd, (void *)path, MIN(strlen(path), MAX_PATH_LEN+1));
    return 0;
}

void TaskPrint()
{
    task_t *task;

    SpinLock(&task_lock);
    KPrint("\n------Task List-------\n");
    list_traversal_all_owner_to_next(task, &task_global_list, global_list)
    {
        KPrint("[debug] task %x\n",task);
        KPrint("name: %s pid=%d ppid=%d status=%d level=%d\n", task->name, (uint32_t)task->pid, (uint32_t)task->parent_pid, (uint32_t)task->status, (uint32_t)task->priority);
    }
    SpinUnlock(&task_lock);
}

int SysTaskStatus(tstatus_t *ts, uint32_t *index)
{
    task_t *task;
    tstatus_t tmp_ts;
    uint32_t n = 0;
    uint32_t idx = 0;

    if (!ts || !index)
        return -EINVAL;

    if (MemCopyFromUser(&idx, index, sizeof(uint32_t)))
        return -EINVAL;

    list_traversal_all_owner_to_next(task, &task_global_list, global_list)
    {
        if (n++ != idx)
            continue;

        // get task status info
        tmp_ts.ts_pid = task->pid;
        tmp_ts.ts_ppid = task->parent_pid;
        tmp_ts.ts_pgid = task->processgroup_id;
        tmp_ts.ts_tgid = task->threadgroup_id;
        tmp_ts.ts_status = task->status;
        tmp_ts.ts_priority = task->priority;
        tmp_ts.ts_timeslice = task->timeslice;
        tmp_ts.ts_runticks = task->elapsed_ticks;
        tmp_ts.ts_uid = task->uid;
        tmp_ts.ts_gid = task->gid;
        memset(tmp_ts.ts_name, 0, PROC_NAME_LEN);
        strcpy(tmp_ts.ts_name, task->name);
        ++idx; // next task

        if (MemCopyToUser(ts, &tmp_ts, sizeof(tstatus_t)))
            return -EINVAL;
        if (MemCopyToUser(index, &idx, sizeof(uint32_t)))
            return -EINVAL;

        return 0;
    }
    return -ESRCH;
}

int SysUnid(int id)
{
    uint32_t _id;
    task_t *cur;

    _id = ((cur->pid & 0xFF) << 8) | ((sys_ticks & 0xFFFF) << 16) | (id & 0xFF);
    return _id;
}

// create init process
void TaskStartUser()
{
    KPrint("[task]: start user process!\n");
    __envp = EnvTranslate(EnvFindByName("PATH"));
    KPrint("[task] create init process\n");
    task_t *proc = ProcessCreate(init_argv, __envp, PROCESS_CREATE_INIT);
    sched_unit_t *su = SchedGetCurUnit();
    if (!su)
    {
        Panic("[task] schedule unit get failed!\n");
    }
    if (!proc)
        Panic("kernel start process failed!\n");
    su->idle->static_priority = su->idle->priority = TASK_PRIOR_LEVEL_LOW;

    KernelDoIdle();
}
